﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Reflection;
using System.Reflection.Emit;
using NetBay.Core.BusinessEntities;

namespace Rio.Framework.Pages.AppCode.Helpers
{
    public abstract class ProxyProperty
    {
        public string PropertyName { get; set; }
        public string DisplayName { get; set; }

        public abstract Type GetPropertyType();
    }
    public static class ProxyBuilder<T, C>
    {
        public class ProxyProperty<U> : ProxyProperty
        {
            public Func<T, string, U> GetValue { get; set; }
            public Action<T, string, U> SetValue { get; set; }

            public override Type GetPropertyType()
            {
                return typeof(U);
            }
        }

        public static IDictionary<string, ProxyProperty> Properties
        {
            get { return _properties; }
        }

        private static readonly IDictionary<string, ProxyProperty> _properties
            = new Dictionary<string, ProxyProperty>();

        public static void AddProperty<U>(string propertyName,
                                            Func<T, string, U> getValue,
                                            Action<T, string, U> setValue,
                                            string displayName = "")
        {
            _properties.Add(propertyName,
                new ProxyProperty<U>
                {
                    PropertyName = propertyName,
                    GetValue = getValue,
                    SetValue = setValue,
                    DisplayName = displayName
                });
        }

        public static void ClearProperties()
        {
            if(_properties != null)
                _properties.Clear();
        }

        public static Type CreateType()
        {
            var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("_Proxy"),
                                                                            AssemblyBuilderAccess.Run);

            var moduleBuilder = assemblyBuilder.DefineDynamicModule("_ProxyModule");

            var typeBuilder = moduleBuilder.DefineType(typeof(T).FullName + "_proxy",
                                                        TypeAttributes.Public | TypeAttributes.Class,
                                                        typeof(ProxyBase<>).MakeGenericType(typeof(T)));

            var innerItemFieldInfo = typeof(ProxyBase<>).MakeGenericType(typeof(T)).GetField("_innerItem", BindingFlags.NonPublic | BindingFlags.Instance);

            foreach (var property in _properties.Values)
            {
                var propertyBuilder = typeBuilder.DefineProperty(property.PropertyName,
                                                                    PropertyAttributes.None,
                                                                    property.GetPropertyType(),
                                                                    Type.EmptyTypes);

                if (!string.IsNullOrEmpty(property.DisplayName))
                {
                    var customAttributeBuilder = new CustomAttributeBuilder(typeof(DisplayAttribute).GetConstructor(Type.EmptyTypes), new object[] { },
                                                                            new PropertyInfo[] { typeof(DisplayAttribute).GetProperty("Name") },
                                                                            new object[] { property.DisplayName });

                    propertyBuilder.SetCustomAttribute(customAttributeBuilder);
                }

                var getter = typeBuilder.DefineMethod("get_" + property.PropertyName,
                                    MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
                                    property.GetPropertyType(),
                                    null);

                var getterIl = getter.GetILGenerator();

                var getValueMethodInfo = typeof(ProxyBuilder<T, C>).GetMethod("GetValue").MakeGenericMethod(property.GetPropertyType());

                getterIl.DeclareLocal(property.GetPropertyType());

                getterIl.Emit(OpCodes.Ldarg_0);
                getterIl.Emit(OpCodes.Ldfld, innerItemFieldInfo);
                getterIl.Emit(OpCodes.Ldstr, property.PropertyName);

                getterIl.Emit(OpCodes.Call, getValueMethodInfo);

                getterIl.Emit(OpCodes.Stloc_0);
                getterIl.Emit(OpCodes.Ldloc_0);

                getterIl.Emit(OpCodes.Ret);

                propertyBuilder.SetGetMethod(getter);

                var setter = typeBuilder.DefineMethod("set_" + property.PropertyName,
                                                        MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
                                                        null,
                                                        new Type[] { property.GetPropertyType() });

                var setterIl = setter.GetILGenerator();

                var setValueMethodInfo = typeof(ProxyBuilder<T, C>).GetMethod("SetValue").MakeGenericMethod(property.GetPropertyType());

                setterIl.Emit(OpCodes.Ldarg_0);
                setterIl.Emit(OpCodes.Ldfld, innerItemFieldInfo);
                setterIl.Emit(OpCodes.Ldstr, property.PropertyName);
                setterIl.Emit(OpCodes.Ldarg_1);

                setterIl.Emit(OpCodes.Call, setValueMethodInfo);

                setterIl.Emit(OpCodes.Ret);

                propertyBuilder.SetSetMethod(setter);
            }

            return typeBuilder.CreateType();
        }

        public static U GetValue<U>(T item, string propertyName)
        {
            if (!_properties.ContainsKey(propertyName))
                throw new Exception();

            return (_properties[propertyName] as ProxyProperty<U>).GetValue(item, propertyName);
        }

        public static void SetValue<U>(T item, string propertyName, U value)
        {
            if (!_properties.ContainsKey(propertyName))
                throw new Exception();

            (_properties[propertyName] as ProxyProperty<U>).SetValue(item, propertyName, value);
        }
    }

    public class ProxyBase<T>
    {
        protected T _innerItem;
        protected Folder _associatedItem;

        [Display(AutoGenerateField = false)]
        public T InnerItem
        {
            get { return _innerItem; }
            set { _innerItem = value; }
        }

        [Display(AutoGenerateField = false)]
        public Folder AssociatedItem
        {
            get { return _associatedItem; }
            set { _associatedItem = value; }
        }
    }
}
